#include <GlobalData.h>
#include "FontAtlas.h"
#include <ft2build.h>
#include <freetype.h>
#include <ftglyph.h>
#include <ftoutln.h>
#include <fttrigon.h>
#include "FTBitmapChar.h"
#include "TreeNode.h"
#include <algorithm>
#include "FTBitmapFont.h"
#include "GLCallBatcher.h"
#include <Engine2d/Engine2d.h>

FontAtlas::FontAtlas(GLCallBatcher *glCallBatcher, uint maxTextureWidth, uint maxTextureHeight) {
	useKerning              = false;
	library                 = NULL;
    this->maxTextureWidth   = maxTextureWidth;
    this->maxTextureHeight  = maxTextureHeight;
    batcher                 = glCallBatcher;
}

FontAtlas::~FontAtlas() {
	int n;
    for (n = 0; n < (int)listBitmapChar.size(); n++) {
        delete listBitmapChar[n];
    }
    for (n = 0; n < (int)listBitmapFont.size(); n++) {
        delete listBitmapFont[n];
    }
	if (showAtlas) {
        delete showAtlas;
        showAtlas = NULL;
    }
	releaseTexture();
	releaseLibrary();
}

void FontAtlas::releaseLibrary() {
	if (library) {
        FT_Done_FreeType(library);
            library = NULL;
	}
}

bool FontAtlas::addFont(/*const char *fontName*/const void* fontData, off_t fontLen, int size, const wchar_t* szLetters) {
	if (library == NULL) {
		if (FT_Init_FreeType( &library ))  {
            LOGE("Couldn't initialise freetype library when trying to add font");
            return false;
        }
	}

	// The Object In Which FreeType Holds Information On A Given
	// Font Is Called A "face".
	FT_Face face;

	// This Is Where We Load In The Font Information From The File.
	int fileSize = fontLen;
	const FT_Byte *buffer = reinterpret_cast<FT_Byte*>((char*)fontData);//reinterpret_cast<FT_Byte*>(Engine2d::loadVFile(fontName, fileSize));
	if (FT_New_Memory_Face(library, buffer, fileSize, 0, &face ))   {
        LOGE("Couldn't find font in file");
        delete [] buffer;
        return false;
    }

	// FreeType Measures Font Size In Terms Of 1/64ths Of Pixels.
	FT_Set_Char_Size(face, size*64, size*64, 72, 72);

	int len = wcslen(szLetters);
	int n;
    
	FTBitmapChar *bitmapChar;
	FT_Glyph pGlyph;
	FTBitmapFont *font = new FTBitmapFont(this);
	font->setLineHeight(face->size->metrics.height >> 6);
	font->setFTFace(face);
	listBitmapFont.push_back(font);

	unsigned wchar_t c;
	int ixGlyph;
    
//    uint firstMissing    = UINT_MAX;
//    uint lastMissing     = UINT_MAX;
    uint missingCounter  = 0;
    
	for (n = 0; n < len; n++) {
		c = szLetters[n];
		// chaeck that the character hasn't already been processed
		if (font->GetChar(c) == NULL) {
			// Load The Glyph For Our Character.
			ixGlyph = FT_Get_Char_Index(face, c);
			if (ixGlyph == 0) {
//                if (firstMissing == UINT_MAX)
//                    firstMissing = static_cast<uint>(c);
//                else if (lastMissing != static_cast<uint>(c)-1) {
//                    printf("\nMissing letters in font %s from %#x to %#x", fontPath, firstMissing, lastMissing);
//                    firstMissing    = UINT_MAX;
//                    lastMissing     = UINT_MAX;
//                }
                //printf("\n-- Missing letter %#x", static_cast<uint>(c));
                ++missingCounter;
//                lastMissing = static_cast<uint>(c);
            }
			else
			{
//                if (firstMissing != UINT_MAX) {
//                    printf("\nMissing letters in font %s from %#x to %#x", fontPath, firstMissing, lastMissing);
//                    firstMissing    = UINT_MAX;
//                    lastMissing     = UINT_MAX;
//                    missingCounter += lastMissing - firstMissing + 1;
//                }
                
				if (FT_Load_Glyph(face, ixGlyph, FT_LOAD_DEFAULT))  {
                    LOGE("\nFT_Load_Glyph failed on character %d of font");
                    delete [] buffer;
                    return false;
                }

				// Move The Face's Glyph Into A Glyph Object.
				if (FT_Get_Glyph(face->glyph, &pGlyph ))  {
                    LOGE("\nFT_Get_Glyph failed on character %d of font");
                    delete [] buffer;
                    return false;
                }

				bitmapChar = new FTBitmapChar(c);
				// all metrics dimensions are multiplied by 64, so we have to divide by 64
				bitmapChar->setOffsets(face->glyph->metrics.horiBearingX >> 6, face->glyph->metrics.horiBearingY >> 6);
				bitmapChar->setSize(face->glyph->metrics.width >> 6, face->glyph->metrics.height >> 6);
				bitmapChar->setXAdvance(face->glyph->metrics.horiAdvance >> 6);
				bitmapChar->setGlyph(pGlyph);
				listBitmapChar.push_back(bitmapChar);
				font->addChar(c, bitmapChar);
			}
		}
	}
//    if (firstMissing != UINT_MAX) {
//        printf("\nMissing letters in font %s from %#x to %#x", fontPath, firstMissing, lastMissing);
//    }
//    LOGE("\nFailed to load %d letters out of %d ", missingCounter, len);

    delete [] buffer;
    return true;
}

// gets next biggest texture size in follwing sequence: 32x32, 64x32, 64x64, 128x64, etc.
bool FontAtlas::getNextTextureSize(int &texWidth, int &texHeight, int ixSize) {
	if (ixSize % 2)
		texHeight *= 2;
	else
		texWidth *= 2;
	if (texWidth > maxTextureWidth || texHeight > maxTextureHeight) {
        printf("\nThe texture required exceeds set limits");
        return false;
    }
    return true;
}

// used by std::sort
bool greaterSize(FTBitmapChar* FTBitmapChar1, FTBitmapChar* FTBitmapChar2) { 
	return FTBitmapChar1->getNumPixels() > FTBitmapChar2->getNumPixels(); 
}

void FontAtlas::createAtlas() {
	int n;
	int totalPixels = 0;
    for (n = 0; n < (int)listBitmapChar.size(); n++) {
        totalPixels += listBitmapChar[n]->getNumPixels();
    }

    int ixSize = 0;
	int texWidth = 32;
	int texHeight = 32;
    while (true) {
    	if (totalPixels <= texWidth*texHeight) {
    		break;
    	}
		getNextTextureSize(texWidth, texHeight, ixSize);
		ixSize++;
    }
	std::sort(listBitmapChar.begin(), listBitmapChar.end(), greaterSize);
	TreeNode::getAllocator().init((int)(listBitmapChar.size()+1)*2);
    while (!binPack(texWidth, texHeight)) {
		TreeNode::getAllocator().deallocate();
		getNextTextureSize(texWidth, texHeight, ixSize);
		ixSize++;
	}
    assert(texWidth <= maxTextureWidth && texHeight <= maxTextureHeight && "\nFont atlas requested needs a bigger texture then defined in maxWidth/maxHeight");
	TreeNode::getAllocator().release();
	unsigned char* pData = new unsigned char[texWidth * texHeight];
#ifdef _DEBUG
	memset(pData, 0, texWidth * texHeight);		// only need this to view atlas
#endif
    for (n = 0; n < (int)listBitmapChar.size(); n++) {
         listBitmapChar[n]->drawToBitmap(pData, texWidth, texHeight);
		 listBitmapChar[n]->releaseGlyph();
    }

    for (n = 0; n < (int)listBitmapFont.size(); n++) {
        listBitmapFont[n]->finishCreating();
    }
	if (!useKerning)
		releaseLibrary();

	glEnable(GL_TEXTURE_2D); 
	glGenTextures(1, &textureID);	
	glBindTexture( GL_TEXTURE_2D, textureID);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texWidth, texHeight, 0 , GL_ALPHA, GL_UNSIGNED_BYTE, pData);
    
    // Cleanup
    delete [] pData;
    pData = NULL;
	showAtlas = new FTBitmapChar();
	showAtlas->setXY(0, 0);
	showAtlas->setSize(texWidth, texHeight);
	showAtlas->initTexCoords(texWidth, texHeight);
}

void FontAtlas::renderAtlas(int x, int y) {
	batcher->setAttribs(textureID, 0, 1.0f, true, true);
	showAtlas->render(x, y, 1.0f, batcher);
}


bool FontAtlas::binPack(int texWidth, int texHeight) {
	TreeNode* pTreeNode = TreeNode::getAllocator().getNew();
	pTreeNode->set(0, 0, texWidth, texHeight); 
    for (int n = 0; n < (int)listBitmapChar.size(); n++) {
		if (!pTreeNode->add(listBitmapChar[n])) {
			return false;
		}
	} 
	return true;
}


void FontAtlas::releaseTexture() {
	// note: make sure you do this before shutting down opengl
	if (textureID != 0)	{
		glDeleteTextures(1, &textureID); 
		textureID = 0;
	}
}
